跳到主要内容

MyBatis 分页

参考资料 MyBatis-Plus 分页查询以及自定义sql分页

MyBatis 分页的方式

首先明确什么是物理分页和逻辑分页。

物理分页:相当于执行了 limit 分页语句,返回部分数据。物理分页只返回部分数据占用内存小,能够获取数据库最新的状态,实施性比较强,一般适用于数据量比较大,数据更新比较频繁的场景。

逻辑分页:一次性把全部的数据取出来,通过程序进行筛选数据。如果数据量大的情况下会消耗大量的内存,由于逻辑分页只需要读取数据库一次,不能获取数据库最新状态,实施性比较差,适用于数据量小,数据稳定的场合。

不过一般讨论的分页都是物理分页

  • 原生的 MyBatis 使用的是 pagehelper 分页插件
  • 或者使用MyBatis-Plus 自带的分页查询

为什么要用分页插件?

如果想要将现有的 select 语句改为支持分页功能的查询语句该怎么做呢?最简单的一种做法就是将所有的 select 语句都加上 limit 来实现分页

select * from t_user limit #{start}, #{pageSize}

这种做法有什么问题呢?

1、要改动的地方非常多,而且每个 sql 改动逻辑基本上一致; 2、DAO 层的查询逻辑要改动,要在原来查询之后执行查询 SELECT count(1) from .... 查询数据总条数,然后才能计算一页的大小和偏移量。

有没有一种简便方法实现呢?

Mybatis 提供了 plugin 机制,允许我们在 Mybatis 的原有处理流程上加入自己逻辑,所有我们就可以使用这种逻辑加上我们的分页逻辑,也就是实现拦截器;

Mybatis 的 plugin 实现原理

pagehelper 分页插件

插件的 项目地址 MyBatis Pagination - PageHelper 插件的 官方文档 HOW TO USE

搭建环境

<!-- 导入 MyBatis 插件,导入这个插件后就无需再导入 MyBatis 了 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>

然后照常配置数据库连接

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/temp_db?useUnicode=true&characterEncoding=utf8&useSSL=true&useServerPrepStmts=true
username: root
password: root

然后加上分页插件的配置

# PageHelper 分页插件配置
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql

最后加上这个打印日志,可以知道 MyBatis 实际执行的 SQL 是什么

mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

编写基本环境代码

要查询的数据如下所示:

首先,编写一个 Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TempUser {
private String name;
private Integer id;
private Integer age;
}

在 DAO 层添加一个分页查找方法,这个查询方法跟查询全部数据的方法除了名称几乎一样。

@Mapper
public interface TempUserMapper {

/**
* 全部查询
*/
@Results(id = "tempUser", value = {
@Result(column = "temp_id", property = "id"),
@Result(column = "temp_name", property = "name"),
@Result(column = "temp_age", property = "age")
}
)
@Select("select * from tb_temp")
List<TempUser> selectAll();

/**
* 分页查询用户,注意它的 SQL 语句也和上面一模一样
*/
@ResultMap(value = "tempUser")
@Select("select * from tb_temp")
List<TempUser> selectPage();
}

这里先编写一个测试类,看看能否执行:

@SpringBootTest
class StudypagehelperApplicationTests {

@Autowired
private TempUserMapper tempUserMapper;

@Test
void contextLoads() {
tempUserMapper.selectAll().forEach(System.out::println);
}

}

输出结果:

配置分页代码

先创建一个分页 Util,用来封装数据(其实不用这个也行,不过一般用这个封装数据,方便取得总页数等其它信息)

@Data
public class PageHelperUtil<T> {
/* 共有数据 */
private Long total;
/* 共有页数 */
private Integer pageTotal;
/* 当前页 */
private Integer page;
/* 每页显示条数 */
private Integer pageSize;
/* 结果集 */
private List<T> list;
}

编写 service 接口

public interface UserService {
PageHelperUtil<TempUser> getUserByPage(Integer page, Integer pageSize);
}

编写 service 接口实现类

// 补充一个知识点:静态导入,如下的 startPage 方法就是通过静态导入进来的,它可以省略类名
import static com.github.pagehelper.page.PageMethod.startPage;

/**
* @author alsritter
* @version 1.0
**/
@Service
public class UserServiceImpl implements UserService {

@Autowired
private TempUserMapper userDao;


@Override
public PageHelperUtil<TempUser> getUserByPage(Integer page, Integer pageSize) {
// 设置起始页以及每页显示数
startPage(page, pageSize);
// 查询这个 selectPage 方法时,实际上插件已经自动分页了
// pagehelper 分页插件的原理是利用 mybatis 拦截器,在查询数据库的时候,拦截下SQL,然后进行修改,从而实现分页
List<TempUser> list = userDao.selectPage();

// 实际上上面的执行的 SQL 已经变成了 select * from tb_temp LIMIT ?, ?
list.forEach(System.out::println); // 这里打印一下查询到的原始数据
System.out.println("=======================================");

// 将查询结果给 pageInfo 处理(就是包装一下结果集,它可以直接取得总页数,总条数等数据)
PageInfo<TempUser> pageInfo = new PageInfo<>(list);
// 创建结果集对象
PageHelperUtil<TempUser> result = new PageHelperUtil<>();

// 将结果封装到结果集对象,当前页
result.setPage(page);
// 每页数
result.setPageSize(pageInfo.getPageSize());
// 总页数
result.setPageTotal(pageInfo.getPages());
// 总条数
result.setPageTotal((int) pageInfo.getTotal());
// 结果集
result.setList(pageInfo.getList());

return result;
}
}

然后编写测试类

@SpringBootTest
class StudypagehelperApplicationTests {

@Autowired
private UserService userService;

@Test
void contextLoads() {
userService.getUserByPage(2,3).getList().forEach(System.out::println);
}
}

输出结果如下:

可以看到这个插件自动处理了这些内容

MyBatis Plus 分页

参考资料 官方文档 分页插件

配置环境

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>

application.yml 配置,和上面一样

# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/temp_db?useUnicode=true&characterEncoding=utf8&useSSL=true&useServerPrepStmts=true
username: root
password: root

# 注意,这里是 plus
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

然后 Entity 和数据都是同上一节,但是因为 MyBatisPlus 是基于 Entity 查询的,所以要对其进行一点修改

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_temp") // 标识要查询的表名
public class TempUser {
@TableField(value = "temp_name")
private String name;
@TableField(value = "temp_id")
@TableId(type = IdType.AUTO, value = "temp_id") // 因为字段名称不一样,所以需要显示指定
private Integer id;
@TableField(value = "temp_age")
private Integer age;
}

配置分页插件

@Configuration
public class MyBatisPlusConfig {

/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

编写个 Mapper 接口

@Mapper
public interface UserMapper extends BaseMapper<TempUser> {
}

先编写一个测试类,看看能否正常运行

@SpringBootTest
class MybatispluspageApplicationTests {

@Autowired
private UserMapper userMapper;


@Test
void contextLoads() {
QueryWrapper<TempUser> wrapper = new QueryWrapper<>();
Page<TempUser> page = new Page<>(1,2);
// userMapper.selectList(wrapper).forEach(System.out::println);
Page<TempUser> tempUserPage = userMapper.selectPage(page, wrapper);

System.out.println("总页数:" + tempUserPage.getPages());
System.out.println("总记录数:" + tempUserPage.getTotal());
System.out.println("查到的数据为:");
tempUserPage.getRecords().forEach(System.out::println);
}
}

输出结果如下: